#include "Camera.h"

namespace lib3dw
{
	const float Camera::PI = 3.14159f;
	const float Camera::_PI_180 = Camera::PI / 180.0f;
	const float Camera::_180_PI = 180.0f / Camera::PI;
	const float Camera::defaultAspect = 4.0f / 3.0f;

	Camera::Camera (Vector3 i_eye, Vector3 i_look, Vector3 i_up, float i_near, float i_far, float i_aspect, float i_angle)
	{
		eye = i_eye;
		defaultEye = i_eye;

		look = i_look;
		defaultLook = i_look;

		up = i_up;
		defaultUp = i_up;

		nearPlane =i_near;
		farPlane = i_far;
		aspect = i_aspect;
		
		phi = 0.0f;
		theta = PI/2.0f;

		weight = 10.0f;
		angle = i_angle;

		frustum = new Frustum();
		SetPerspective();
		SetPosition(eye, look);
	}

	void Camera::Reset()
	{
		eye = defaultEye;
		look = defaultLook;
		up = defaultUp;

		SetModelView();
	}

	void Camera::ResizeWindow (int ww, int wh)
	{
		windowWidth = ww;
		windowHeight = wh;

		SetViewport();

		aspect = windowWidth / (float) windowHeight;

		SetPerspective();
	}

	void Camera::SetViewport()
	{
		glViewport(0, 0, windowWidth, windowHeight);
	}


	void Camera::SetModelView()
	{
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();
		gluLookAt(eye.x, eye.y, eye.z, look.x, look.y, look.z, up.x, up.y, up.z);
		frustum->SetCamera(eye, look, up);
	}
	
	void Camera::SetPerspective()
	{
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		gluPerspective(angle, aspect, nearPlane, farPlane);
		frustum->SetViewport(angle, aspect, nearPlane, farPlane);
	}

	void Camera::SetOrthogonal()
	{
	}

	float Camera::TransformMouseX()
	{
		return ((2*PI)/(float)windowWidth);
	}


	float Camera::TransformMouseY()
	{
		return (PI/(float)windowHeight);	
	}

	Vector3 Camera::CalculateN() const
	{
		return (eye-look);
	}

	Vector3 Camera::CalculateU() const
	{
		Vector3 n = CalculateN();
		Vector3 u = up;
		return u.cross(n);
	}

	Vector3 Camera::CalculateV() const
	{
		Vector3 n = CalculateN();
		Vector3 u = CalculateU();
		return n.cross(u);
	}

	Vector3 Camera::CalculateXYZ() const
	{
		Vector3 tmp;
		tmp.x = (float) (sin (theta) * sin (phi));
		tmp.y = (float) (cos (theta));
		tmp.z = (float) (sin (theta) * cos (phi));

		return tmp;
	}

	// public
	void Camera::Move (float hAngle, float vAngle)
	{
		if(hAngle != 0)
		{
			Vector3 u = CalculateU();
			u.normalize();

			eye += -hAngle * u;
			look+= -hAngle * u;
		}

		if(vAngle != 0)
		{
			Vector3 v = CalculateV();
			v.normalize();

			eye += vAngle * v;
			look+= vAngle * v;
		}

		SetModelView();
	}
	
	void Camera::MoveHorizontal (float amount)
	{
		Move(amount, 0);
	}
	
	void Camera::MoveVertical (float amount)
	{
		Move(0,amount);
	}

	void Camera::Rotate(float hAngle, float vAngle)
	{
		if(hAngle != 0)
		{
			float oldPhi = phi;
			float newPhi = phi;

			if(up.y > 0)
			{
				newPhi += -hAngle;
			}
			else 
			{
				if (up.y < 0)
				{
					newPhi += hAngle;
				}
			}

			if (oldPhi >= 0 && newPhi < 0)
			{
				newPhi = 2*PI + newPhi;
			}
			else 
			{
				if (oldPhi <= 2*PI && newPhi > 2*PI)
				{
					newPhi = newPhi - 2*PI;
				}
			}

			phi = newPhi;
		}

		if(vAngle != 0)
		{
			float oldTheta = theta;
			float newTheta = theta;

			newTheta += -vAngle;

			if (oldTheta > 0 && newTheta <= 0)
			{
				if(oldTheta != 0)
				{
					newTheta = 2*PI + newTheta;
				}
				else
				{
					newTheta = 2*PI - 0.01f;
				}
				up.flip();
			}
			else
			{
				if (oldTheta < 2*PI && newTheta >= 2*PI)
				{
					newTheta = 2*PI - newTheta;
					if (newTheta == 0)
					{
						newTheta = 0.01f;
					}
					up.flip();
				}
				else
				{
					if (oldTheta <= PI && newTheta > PI)
					{
						up.flip();
					}
					else
					{
						if (oldTheta >= PI && newTheta < PI)
						{
							up.flip();
						}
					}
				}
			}
			theta = newTheta;
		}

		Vector3 dir = CalculateXYZ();
		dir.normalize ();
		Vector3 forward = eye-look;
		eye = look + weight*dir;

		SetModelView();
	}

	void Camera::RotateHorizontal(float amount)
	{
		Rotate(amount, 0);
	}

	void Camera::RotateVertical(float amount)
	{
		Rotate(0 ,amount);
	}

	void Camera::Zoom(float amount)
	{
		Vector3 dir = CalculateXYZ();
		dir.normalize();

		eye += -amount * weight * dir;
		look+= -amount * weight * dir;

		SetModelView();
	}

	Vector3 Camera::GetLook() const
	{
		return look;
	}
	
	Vector3 Camera::GetEye() const
	{
		return eye;
	}
	
	Vector3 Camera::GetUp() const
	{
		return up;
	}

	float Camera::GetPhi() const
	{
		return phi;
	}
	
	float Camera::GetTheta() const
	{
		return theta;
	}
	
	float Camera::GetNear() const
	{
		return nearPlane;
	}
	
	float Camera::GetFar() const
	{
		return farPlane;
	}
	
	float Camera::GetAngle() const
	{
		return angle;
	}

	float Camera::GetAspect() const
	{
		return aspect;
	}

	void Camera::SetPosition (Vector3 i_eye, Vector3 i_look)
	{
		eye = i_eye;
		look = i_look;

		if(eye == look)
		{
			eye = Vector3(0,0,1);
			look = Vector3(0,0,0);
		}

		Vector3 n = CalculateN();
		weight = n.length();
		n.normalize();

		theta = (float) (acos(n.y));

		if (n.z==0)
		{
			n.z = 0.1f;
		}

		phi = atan2(n.x,n.z);

		up.y = 1.0f;

		SetModelView();
	}

	Frustum* Camera::GetFrustum()
	{
		return frustum;
	}
}
